//
//  read.cpp
//  
//
//  Created by Nikita Luzyanin on 30.11.13.
//
//

#include <iostream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <stdio.h>
#include "functions.cpp"
using namespace std;

int readRaidType0(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    char diskName[10];
    char diskNumber[10];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    int bytesWritten = 0;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        char helperChar[6], ch;
        int lengthOfPartOfFile, helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthOfPartOfFile = atoi(helperChar);
        lengthOfFile = lengthOfFile + lengthOfPartOfFile;
        for(int i = 0; i < lengthOfPartOfFile; i++) {
            ch = RFD.get();
            file[bytesWritten] = ch;
            bytesWritten ++;
        }
        RFD.close();
    }
    
    return 0;
}

int readRaidType1(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    char diskName[10];
    char diskNumber[10];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    char readFile[numberOfDisks][2048];
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        
        char helperChar[6], ch;
        int helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthOfFile = atoi(helperChar);
        for(int i = 0; i < lengthOfFile; i++) {
            ch = RFD.get();
            if (numberOfDisk == 0) {
                file[i] = ch;
            } else {
                if (file[i] != ch) {
                    cout << "Not equal files on disks\n";
                    return 1;
                }
                readFile[numberOfDisk][i] = ch;
            }
        }
        RFD.close();
    }
    return 0;
}

int readRaidType3(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char dataFromEachDisk[numberOfDisks][2048];
    int lengthForEachDisk[numberOfDisks];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        
        char helperChar[6], ch;
        int helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthForEachDisk[numberOfDisk] = atoi(helperChar);
        for(int i = 0; i < lengthForEachDisk[numberOfDisk]; i++) {
            ch = RFD.get();
            dataFromEachDisk[numberOfDisk][i] = ch;
        }
        RFD.close();
        // cout << numberOfDisk << "\n" << lengthForEachDisk[numberOfDisk] << "\n" << dataFromEachDisk[numberOfDisk] << "\n";
    }
    
    for (int numberOfDisk = 1; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        if (lengthForEachDisk[0] > lengthForEachDisk[numberOfDisk]) {
            dataFromEachDisk[numberOfDisk][lengthForEachDisk[numberOfDisk]] = '0';
        }
    }
    
    if (checkParity(dataFromEachDisk[disksForData], dataFromEachDisk, lengthForEachDisk[0], numberOfDisks, disksForData) != 0) {
        return 1;
    }
    
    lengthOfFile = 0;
    for (int i = 0; i < disksForData; i++) {
        lengthOfFile = lengthOfFile + lengthForEachDisk[i];
    }
    
    int bytesWritten = 0;
    while ((bytesWritten < lengthOfFile)) {
        int helper = bytesWritten / disksForData;
        int diskToRead = bytesWritten - helper * disksForData;
        
        file[bytesWritten] = dataFromEachDisk[diskToRead][helper];
        bytesWritten ++;
    }
    
    return 0;
}

int readRaidType4(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char dataFromEachDisk[numberOfDisks][2048];
    int lengthForEachDisk[numberOfDisks];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        
        char helperChar[6], ch;
        int helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthForEachDisk[numberOfDisk] = atoi(helperChar);
        for(int i = 0; i < lengthForEachDisk[numberOfDisk]; i++) {
            ch = RFD.get();
            dataFromEachDisk[numberOfDisk][i] = ch;
        }
        RFD.close();
        // cout << numberOfDisk << "\n" << lengthForEachDisk[numberOfDisk] << "\n" << dataFromEachDisk[numberOfDisk] << "\n";
    }
    
    for (int numberOfDisk = 1; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        if (lengthForEachDisk[0] > lengthForEachDisk[numberOfDisk]) {
            dataFromEachDisk[numberOfDisk][lengthForEachDisk[numberOfDisk]] = '0';
        }
    }
    
    if (checkParity(dataFromEachDisk[disksForData], dataFromEachDisk, lengthForEachDisk[0], numberOfDisks, disksForData) != 0) {
        return 1;
    }
    
    lengthOfFile = 0;
    int bytesWritten = 0;
    for (int numberOfDisk = 0; numberOfDisk < disksForData; numberOfDisk++) {
        for (int i = 0; i < lengthForEachDisk[numberOfDisk]; i ++) {
            file[bytesWritten] = dataFromEachDisk[numberOfDisk][i];
            bytesWritten ++;
            lengthOfFile ++;
        }
    }
    
    return 0;
}

int readRaidType5(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    char diskName[10];
    char diskNumber[10];
    int disksForData = numberOfDisks - 1;
    char dataFromEachDisk[numberOfDisks][2048];
    int lengthForEachDisk[numberOfDisks];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        
        char helperChar[6], ch;
        int helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthForEachDisk[numberOfDisk] = atoi(helperChar);
        for(int i = 0; i < lengthForEachDisk[numberOfDisk]; i++) {
            ch = RFD.get();
            dataFromEachDisk[numberOfDisk][i] = ch;
        }
        RFD.close();
        // cout << numberOfDisk << "\n" << lengthForEachDisk[numberOfDisk] << "\n" << dataFromEachDisk[numberOfDisk] << "\n";
    }
    
    for (int numberOfDisk = 1; numberOfDisk < numberOfDisks; numberOfDisk ++) {
        if (lengthForEachDisk[0] > lengthForEachDisk[numberOfDisk]) {
            dataFromEachDisk[numberOfDisk][lengthForEachDisk[numberOfDisk]] = '0';
        }
    }
    
    if (checkParity(dataFromEachDisk[disksForData], dataFromEachDisk, lengthForEachDisk[0], numberOfDisks, disksForData) != 0) {
        return 1;
    }
    
    lengthOfFile = 0;
    int bytesWritten = 0;
    int helper = numberOfBlockToRead / numberOfDisks;
    int diskWithParity = numberOfBlockToRead - helper * (numberOfDisks - 1);
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisks; numberOfDisk++) {
        if (numberOfDisk != diskWithParity) {
            for (int i = 0; i < lengthForEachDisk[numberOfDisk]; i ++) {
                file[bytesWritten] = dataFromEachDisk[numberOfDisk][i];
                bytesWritten ++;
                lengthOfFile ++;
            }
        }
    }
    
    return 0;
}

int readRaidType01(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    char diskName[10], diskNumber[10];
    char checkDiskName[10];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    int bytesWritten = 0;
    
    for (int numberOfDisk = 0; numberOfDisk < numberOfDisksFor0Type; numberOfDisk++) {
        // place for check with copies
        sprintf(diskNumber, "%i", numberOfDisk);
        strcpy(diskName, "disk");
        strcat(diskName, diskNumber);
        
        sprintf(diskNumber, "%i", numberOfDisk + numberOfDisksFor0Type);
        strcpy(checkDiskName, "disk");
        strcat(checkDiskName, diskNumber);
        
        if (checkEqualityOfBlocks(diskName, checkDiskName, positionToReadFrom) != 0) {
            cout << "Disk " << diskName << " and his backup disk " << checkDiskName << " are not equal\n";
            return 1;
        }
        
        RFD.open(diskName);
        RFD.seekp(positionToReadFrom);
        char helperChar[6], ch;
        int lengthOfPartOfFile, helperInt;
        helperInt = 0;
        ch = RFD.get();
        while (ch != '\n') {
            helperChar[helperInt] = ch;
            helperInt ++;
            ch = RFD.get();
        }
        lengthOfPartOfFile = atoi(helperChar);
        lengthOfFile = lengthOfFile + lengthOfPartOfFile;
        for(int i = 0; i < lengthOfPartOfFile; i++) {
            ch = RFD.get();
            file[bytesWritten] = ch;
            bytesWritten ++;
        }
        RFD.close();
    }
    
    return 0;
}

int readRaidType10(char file[2048], int &lengthOfFile, int lengthOfMetaBlock, int numberOfBlockToRead, int numberOfDisks, int sizeOfBlock)
{
    fstream RFD;
    int numberOfDisksFor0Type = numberOfDisks / 2;
    int numberOfDisksFor1Type = numberOfDisks / 2;
    char diskName[10], diskNumber[10];
    char checkDiskName[10];
    int positionToReadFrom = lengthOfMetaBlock + numberOfBlockToRead * sizeOfBlock + 1;
    int bytesWritten = 0;
    bool firstRun = true;
    
    for (int i = 0; i < numberOfDisksFor1Type; i ++) {
        int bytesWritten = 0;
        lengthOfFile = 0;
        for (int numberOfDisk = i; numberOfDisk < numberOfDisks; numberOfDisk = numberOfDisk + numberOfDisksFor0Type) {
            
            sprintf(diskNumber, "%i", numberOfDisk);
            strcpy(diskName, "disk");
            strcat(diskName, diskNumber);
            RFD.open(diskName);
            RFD.seekp(positionToReadFrom);
                        
            char helperChar[6], ch;
            int lengthOfPartOfFile, helperInt;
            helperInt = 0;
            ch = RFD.get();
            while (ch != '\n') {
                helperChar[helperInt] = ch;
                helperInt ++;
                ch = RFD.get();
            }
            lengthOfPartOfFile = atoi(helperChar);
            lengthOfFile = lengthOfFile + lengthOfPartOfFile;
            if (firstRun == true) {
                for(int i = 0; i < lengthOfPartOfFile; i++) {
                    ch = RFD.get();
                    file[bytesWritten] = ch;
                    bytesWritten ++;
                }
            } else {
                for(int i = 0; i < lengthOfPartOfFile; i++) {
                    ch = RFD.get();
                    if (file[bytesWritten] != ch) {
                        cout << "Disk and his backup disk are not equal\n";
                        return 1;
                    }
                    bytesWritten ++;
                }
            }
            RFD.close();
        }
        firstRun = false;
    }
    
    return 0;
}

int main(int argc, char* argv[])
{
    int numberOfDisks, numberOfBlocks, sizeOfBlock, lengthOfMetaBlock;
    char raidType[2];
    char missingDisksNames[5][100];
    int numberOfDisksMissing;
    if (checkDisks(numberOfDisks, numberOfBlocks, sizeOfBlock, raidType, lengthOfMetaBlock, missingDisksNames, numberOfDisksMissing) != 0) {
        return 1;
    } else if (numberOfDisksMissing > 0) {
        cout << numberOfDisksMissing << " disks are missing\n";
        return 1;
    }
    
    int numberOfBlockToRead = atoi(argv[1]);
    if ((numberOfBlockToRead < 0)||(numberOfBlockToRead > numberOfBlocks)) {
        cout << "No block with such number\n";
        return 1;
    }
    
    if (checkExistanceOfBlock(numberOfDisks, sizeOfBlock, numberOfBlockToRead, lengthOfMetaBlock) != 0 ) {
        return 1;
    }
    
    char pathToFile[1000];
    strcpy(pathToFile, argv[2]);

    char file[2048], ch;
    int lengthOfFile = 0;
    
    bool completed = true;
    if (strcmp(raidType, "0") == 0) {
        if (readRaidType0(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "1") == 0) {
        if (readRaidType1(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            cout << "Read files are different\n";
            completed = false;
        }
    } else if (strcmp(raidType, "3") == 0) {
        if (readRaidType3(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "4") == 0) {
        if (readRaidType4(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if (strcmp(raidType, "5") == 0) {
        if (readRaidType5(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '0')&&(raidType[1] == '1')) {
        if (readRaidType01(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    } else if ((raidType[0] == '1')&&(raidType[1] == '0')) {
        if (readRaidType10(file, lengthOfFile, lengthOfMetaBlock, numberOfBlockToRead, numberOfDisks, sizeOfBlock) != 0) {
            completed = false;
        }
    }
    
    if (completed == false) {
        cout << "Mistake while reading\n";
        return 1;
    }
    
    ofstream FS;
    FS.open(pathToFile);
    for (int i = 0; i < lengthOfFile; i ++) {
        FS << file[i];
    }
    FS.close();
    
    return 0;
}